/*
 * Copyright (C) 2012  Paulo Cesar Pereira de Andrade.
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * Authors:
 *	Paulo Cesar Pereira de Andrade
 */

#include "exl.h"

/*
 * Prototypes
 */
static void
collect_ast(east_t *ast);

static ebool_t
get_number(eobject_t *pointer, etype_t type);

static void
collect_number(eobject_t object, etype_t type);

/*
 * Initialization
 */
static east_t		*cache_ast;
static evector_t	*cache_number;

/*
 * Implementation
 */
void
init_cache(void)
{
    eadd_root((eobject_t *)&cache_ast);

    eadd_root((eobject_t *)&cache_number);
    enew_vector((eobject_t *)&cache_number, t_void, t_mpc + 1);
}

void
finish_cache(void)
{
    erem_root((eobject_t *)&cache_ast);
    erem_root((eobject_t *)&cache_number);
}

void
edel_object(eobject_t *pointer)
{
    etype_t		type;
    eobject_t		object;

    if ((object = *pointer)) {
	switch (type = etype(object)) {
	    case t_ast:
		collect_ast(object);
		break;
	    case t_word:	case t_float:		case t_rat:
	    case t_mpz:		case t_mpq:		case t_mpr:
	    case t_cdd:		case t_cqq:		case t_mpc:
		collect_number(object, type);
		break;
	    default:
		/* add support for extra fast recycling objects here */
		object = null;
		break;
	}
	*pointer = null;
#if CACHE_DEBUG
	gc_cache_debug(object);
#endif
    }
}

east_t *
eget_ast(eobject_t *pointer)
{
    east_t		*next;

    if (cache_ast) {
	*pointer = cache_ast;
	next = cache_ast->next;
	cache_ast->next = null;
	cache_ast = next;
    }
    else
	enew_object(pointer, t_ast, sizeof(east_t));
#if CACHE_DEBUG
    gc_cache_debug(null);
#endif

    return ((east_t *)*pointer);
}

void
eget_word(eobject_t *pointer)
{
    if (!get_number(pointer, t_word))
	enew_word(pointer, 0);
#if CACHE_DEBUG
	gc_cache_debug(null);
#endif
}

void
eget_float(eobject_t *pointer)
{
    if (!get_number(pointer, t_float))
	enew_float_w(pointer, 0);
#if CACHE_DEBUG
	gc_cache_debug(null);
#endif
}

void
eget_rat(eobject_t *pointer)
{
    if (!get_number(pointer, t_rat))
	enew_rat_w(pointer, 0);
#if CACHE_DEBUG
	gc_cache_debug(null);
#endif
}

void
eget_mpz(eobject_t *pointer)
{
    if (!get_number(pointer, t_mpz))
	enew_mpz_w(pointer, 0);
#if CACHE_DEBUG
	gc_cache_debug(null);
#endif
}

void
eget_mpq(eobject_t *pointer)
{
    if (!get_number(pointer, t_mpq))
	enew_mpq_w(pointer, 0);
#if CACHE_DEBUG
	gc_cache_debug(null);
#endif
}

void
eget_mpr(eobject_t *pointer)
{
    if (!get_number(pointer, t_mpr))
	enew_mpr_w(pointer, 0);
#if CACHE_DEBUG
	gc_cache_debug(null);
#endif
}

void
eget_cdd(eobject_t *pointer)
{
    if (!get_number(pointer, t_cdd))
	enew_cdd_w(pointer, 0);
#if CACHE_DEBUG
	gc_cache_debug(null);
#endif
}

void
eget_cqq(eobject_t *pointer)
{
    if (!get_number(pointer, t_cqq))
	enew_cqq_w(pointer, 0);
#if CACHE_DEBUG
	gc_cache_debug(null);
#endif
}

void
eget_mpc(eobject_t *pointer)
{
    if (!get_number(pointer, t_mpc))
	enew_mpc_w(pointer, 0);
#if CACHE_DEBUG
	gc_cache_debug(null);
#endif
}

static void
collect_ast(east_t *ast)
{
    edel_object(&ast->l.value);
    edel_object(&ast->r.value);
    edel_object(&ast->t.value);
    edel_object(&ast->c.value);
    edel_object((eobject_t *)&ast->next);
    ast->code = 0;
    ast->type = t_void;
    ast->next = cache_ast;
    cache_ast = ast;
}

static ebool_t
get_number(eobject_t *pointer, etype_t type)
{
    evector_t		*vector;

    if ((vector = cache_number->v.ptr[type]) && vector->offset) {
	*pointer = vector->v.ptr[--vector->offset];
#if CACHE_DEBUG
	vector->v.ptr[vector->offset] = null;
#endif

	return (true);
    }

    return (false);
}

static void
collect_number(eobject_t object, etype_t type)
{
    evector_t		*vector;

    if ((vector = cache_number->v.ptr[type]) == null) {
	enew_vector(cache_number->v.ptr + type, t_void, 8);
	vector = cache_number->v.ptr[type];
    }
    else if (vector->offset >= vector->length)
	erenew_vector(vector, vector->length + 8);
    vector->v.ptr[vector->offset++] = object;
}
